<?php
// +----------------------------------------------------------------------
// | SwiftPHP [ JUST DO ONE THING WELL ]
// +----------------------------------------------------------------------
// | Copyright (c) 2015-2017  http://swiftphp.zhongwenyu.com All rights reserved.
// +----------------------------------------------------------------------
// | Author: zhongwenyu <zhongwenyu1987@163.com> <http://www.zhongwenyu.com>
// +----------------------------------------------------------------------
// | Times: 2017/2/25 15:57
// +----------------------------------------------------------------------
namespace swift;

class Model{
    // 数据库连接池
    protected static $links = [];
    // 数据库连接配置
    protected $connection = [];
    // 当前模型查询对象
    protected $query;
    // 数据表名称
    protected $table;
    // 字段验证规则，['name' => ['require']]
    protected $validate = null;
    // 数据表主键，默认自动获取
    protected $primary_key;
    // 当前模型数据信息
    protected $data;
    // 当前类名
    protected $class;
    // 当前模型名
    protected $name;
    // 字段类型或者格式转换
    protected $type = [];
    // 错误信息
    protected $error;
    // 时间字段取出后的默认时间格式
    protected $dateFormat;
    // 软删除字段名
    protected $softfield = 'deltime';
    // 初始化过的模型
    protected static $initialized = [];

    /**
     * 架构函数
     * @access public
     * @param array|object $data 数据
     * @param array|object $table 表名
     */
    public function __construct(array $data = [] , $table = ''){
        // 模型对象赋值
        $this->data($data , true);
        $this->class = get_class($this);

        // 数据库连接设置
        $this->connection = array_merge(Config::get('database'), $this->connection);

        // 模型名
        if(empty($this->name)){
            $name = str_replace('\\' , '/' , $this->class);
            $this->name = basename($name);
        }

        // 设置表名
        if(empty($table)){
            $table = empty($this->table) ? strtolower($this->name) : $this->table;
        }
        $this->table = strpos($table , $this->connection['prefix']) === false ? $this->connection['prefix'] . $table : $table;

        // 执行初始化操作
        $this->initialize();
    }

    /**
     * 返回错误信息
     * @return error
     */
    public function getError(){
        return $this->error;
    }

    /**
     * 初始化模型
     * @access protected
     * @return void
     */
    protected function initialize(){
        if(!isset(self::$initialized[$this->class])){
            self::$initialized[$this->class] = $this;
        }
    }

    /**
     * 获取当前模型的数据库查询对象
     * @access public
     * @return Query
     */
    public function db(){
        $model = $this->name;
        if(!isset(self::$links[$model])){
            // 初始化模型查询对象
            $query = Db::connect($this->connection)->getQuery($model);
            // 设置表名
            if(!empty($this->table)){
                $query->setTable($this->table);
            }else{
                $query->setTable(strtolower($model));
            }
            // 设置表主键
            if(empty($this->primary_key)){
                $this->primary_key = $query->getPk();
            }
            self::$links[$model] = $query;
        }
        // 返回当前模型的数据库查询对象
        return self::$links[$model];
    }

    /**
     * model静态初始化
     * @access public
     * @param array  $table   表名
     * @return array $data   数据
     */
    public static function init($data = []){
        $class = get_called_class();
        if(strpos($class , '\\') !== false){
            $name = str_replace('\\' , '/' , $class);
            $name = basename($name);
        }else{
            // 默认app\model
            $name = $class;
            $class = Config::get('app_namespace') . '\\model\\' . $class;
        }
        if(!isset(static::$initialized[$name])){
            self::$initialized[$name] = new $class($data);
        }
        return self::$initialized[$name];
    }

    /**
     * 数据存入筛选
     * @access public
     * @param array  $data     数据
     * @return array $data
     */
    protected function dataInputScreen(&$data){
        if(!empty($data)){
            $data = $this->data($data)->data;
        }else{
            $data = $this->data;
        }

        // 自动验证
        if(!empty($this->validate)){
            $result = $this->validateData($data);
            if($result !== true){
                return false;
            }
        }
        $fields = $this->db()->getFields();
        // 检测字段
        foreach($data as $k => $v){
            if(!in_array($k , $fields)){
                unset($data[$k]);
            }
            // 去除主键
            $pk = $this->db()->getPk();
            if(isset($data[$pk])){
                unset($data[$pk]);
            }
        }
        return true;
    }

    /**
     * 写入一条数据对象
     * @access public
     * @param array  $data     数据
     * @return integer|false
     */
    public function add($data = []){
        // 数据筛选
        $result = $this->dataInputScreen($data);
        if($result !== true){
            return false;
        }
        if(!empty($data)){
            return $this->db()->insert($data);
        }
        return false;
    }

    /**
     * 写入多条数据对象
     * @access public
     * @param array  $datas     数据
     * @return integer|false
     */
    public function addAll($datas = []){
        foreach($datas as $data){
            // 数据筛选
            $result = $this->dataInputScreen($data);
            if($result !== true){
                return false;
            }
        }
        if(!empty($data)){
            return $this->db()->insertAll($datas);
        }
        return false;
    }

    /**
     * 更新数据对象
     * @access public
     * @param array  $data     数据
     * @param array  $where    更新条件
     * @return integer|false
     */
    public function save($data = [] , $where = []){
        if(!empty($where)){
            $this->db()->where($where);
        }

        // 软删除筛选
        $this->softdel();

        // 数据筛选
        $result = $this->dataInputScreen($data);
        if($result !== true){
            return false;
        }

        if(!empty($data)){
            return $this->db()->update($data);
        }
        return false;
    }

    /**
     * 删除数据对象
     * @access public
     * @param array  $where    删除条件
     * @param int    $soft     是否软删除，默认true
     * @return integer|false
     */
    public function del($where = [] , $soft = true){
        if(!empty($where)){
            $this->where($where);
        }

        // 软删除筛选
        $this->softdel();

        if($soft !== false){
            // 查询软删除字段是否存在表中
            $fields = $this->db()->getFields();
            if(!in_array($this->softfield , $fields)){
                // 字段不存在
                $res = $this->db()->addColumn($this->softfield , 'int(12)' , 0);
            }
            $data[$this->softfield] = time();
            $result = $this->db()->update($data);
        }else{
            // 物理删除
            $result = $this->db()->delete();
        }
        return $result;
    }

    /**
     * 数据取出筛选，支持多维数组
     * @access public
     * @param array  $data     数据
     * @return array $data
     */
    protected function dataOutputScreen($data){
        $arr = [];
        if(!empty($data)){
            if(is_numeric(key($data))){
                // 多维数组
                foreach($data as $key => $v){
                    $arr[$key] = $this->dataOutputScreen($v);
                }
            }else{
                // 数据类型转换
                array_walk($data , [$this , 'readTrans'] , $data);
                $arr = $data;
            }
        }
        return $arr;
    }

    /**
     * 读取数据转换
     * @param string  $value   字段值
     * @param string  $name   字段名
     * @param array  $data   数据集
     */
    protected function readTrans(&$value , $name , $data){
        // 检测获取器
        $method = 'get' . ucfirst($name) . 'Attr';
        if (method_exists($this, $method)) {
            $value = $this->$method($value, $data);
        } elseif (isset($this->type[$name])) {
            // 类型转换
            $value = $this->writeTransform($value, $this->type[$name]);
        }
    }

    /**
     * 设置查询字段
     * @access public
     * @param array|string  $field   查询字段
     * @param bool          $flag    是否设置不查询字段
     * @return $this
     */
    public function field($field , $flag = false){
        if(!empty($field)){
            $this->db()->field($field , $flag);
        }
        return $this;
    }

    /**
     * 设置排序
     * @access public
     * @param string  $order   排序
     * @return $this
     */
    public function order($order = ''){
        if(!empty($order)){
            $this->db()->order($order);
        }
        return $this;
    }

    /**
     * 设置limit
     * @access public
     * @param mixed $offset 起始位置
     * @param mixed $length 查询数量
     * @return $this
     */
    public function limit($offset = null, $length = null){
        if(!is_null($offset)){
            $this->db()->limit($offset , $length);
        }
        return $this;
    }

    /**
     * 查询一条数据对象
     * @access public
     * @param array  $where    查询条件
     * @return array 查询结果
     */
    public function get($where = []){
        if(!is_null($where)){
            $this->where($where);
        }

        // 软删除筛选
        $this->softdel();

        // 获取查询数据
        $result = $this->db()->find();

        // 输出数据筛选
        $result = $this->dataOutputScreen($result);

        return $result;
    }

    /**
     * 软删除筛选
     */
    protected function softdel(){
        $allFields = $this->db()->getFields();
        if(in_array($this->softfield , $allFields)){
            if(!empty($this->db()->getWhere())){
                $where[] = [$this->softfield , '=' , 0 , 'and'];
            }else{
                $where[] = [$this->softfield , '=' , 0];
            }
            $this->db()->where($where);
        }
    }

    /**
     * 查询多条数据对象
     * @access public
     * @param array  $where    查询条件
     * @return array 查询结果
     */
    public function all($where = []){
        if(!empty($where)){
            $this->where($where);
        }

        // 软删除筛选
        $this->softdel();

        // 获取查询数据
        $result = $this->db()->select();

        // 输出数据筛选
        $result = $this->dataOutputScreen($result);

        return $result;
    }

    /**
     * 执行查询sql
     * @params string $sql
     * @params array  $bind
     * @return array
     */
    public function query($sql , $bind = []){
        return Db::query($sql , $bind);
    }

    /**
     * 返回sql语句
     */
    public function fetchSql(){
        $this->db()->fetchSql = true;
        return $this;
    }

    /**
     * 设置查询条件
     * @access public
     * @param mixed $field 字段名
     * @param mixed $condition 查询表达式
     * @param mixed $value 字段值
     * @param mixed $type 多个查询条件连接条件
     * @return $this
     */
    public function where($field , $condition = '' , $value = '' , $type = ''){
        $this->db()->where($field , $condition , $value , $type);
        return $this;
    }

    /**
     * COUNT查询
     * @access public
     * @return integer|string
     */
    public function count(){
        // 软删除筛选
        $this->softdel();
        return $this->db()->count();
    }

    /**
     * SUM查询
     * @access public
     * @param string $field 字段名
     * @return integer|string
     */
    public function sum($field){
        // 软删除筛选
        $this->softdel();
        return $this->db()->sum($field);
    }

    /**
     * MAX查询
     * @access public
     * @param string $field 字段名
     * @return integer|string
     */
    public function max($field){
        // 软删除筛选
        $this->softdel();
        $result = $this->db()->max($field);
        return $this->dataOutputScreen($result);
    }

    /**
     * MIN查询
     * @access public
     * @param string $field 字段名
     * @return integer|string
     */
    public function min($field){
        // 软删除筛选
        $this->softdel();
        $result = $this->db()->min($field);
        return $this->dataOutputScreen($result);
    }

    /**
     * AVG查询
     * @access public
     * @param string $field 字段名
     * @return integer|string
     */
    public function avg($field){
        // 软删除筛选
        $this->softdel();
        $result = $this->db()->avg($field);
        return $this->dataOutputScreen($result);
    }

    /**
     * 自动验证数据，支持批量验证
     * @access protected
     * @param  array $data      验证数据
     * @param  string|array $rules     验证规则
     * @return bool
     */
    protected function validateData($data , $rules = null){
        if(is_string($rules) && isset($this->validate[$rules])){
            $rules = $this->validate[$rules];
        }
        if(!empty($this->validate))
            $rules = $this->validate;
        if(!is_null($rules)){
            $validate = new Validate($rules);
            if(!$validate->check($data)){
                $this->error = $validate->getError();
                return false;
            }
        }
        return true;
    }

    /**
     * 获取模型对象的主键
     * @access public
     * @param string $name 模型名
     * @return mixed
     */
    public function getPk($name = ''){
        if(empty($this->primary_key)){
            $this->primary_key = $this->db()->getPk();
        }
        return $this->primary_key;
    }

    /**
     * 判断一个字段名是否为主键字段
     * @access public
     * @param string $key 名称
     * @return bool
     */
    protected function isPk($key)
    {
        $pk = $this->getPk();
        if (is_string($pk) && $pk == $key) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 设置数据对象值
     * @access public
     * @param mixed $data  数据或者属性名
     * @param mixed $value 值
     * @return $this
     */
    public function data($data, $value = null){
        if (is_string($data)) {
            $this->data[$data] = $value;
        } else {
            if (true === $value) {
                // 数据对象赋值
                foreach ($data as $key => $value) {
                    $this->setAttr($key, $value, $data);
                }
            } else {
                $this->data = $data;
            }
        }
        return $this;
    }

    /**
     * 获取对象原始数据 如果不存在指定字段返回false
     * @access public
     * @param string $name 字段名 留空获取全部
     * @return mixed
     * @throws \Exception
     */
    public function getData($name = null)
    {
        if (is_null($name)) {
            return $this->data;
        } elseif (array_key_exists($name, $this->data)) {
            return $this->data[$name];
        } else {
            throw new \Exception('property not exists:' . $this->class . '->' . $name);
        }
    }

    /**
     * 修改器 设置数据对象值
     * @access public
     * @param string $name  属性名
     * @param mixed  $value 属性值
     * @param array  $data  数据
     * @return $this
     */
    public function setAttr($name, $value, $data = []){
        // 检测修改器
        $method = 'set' . ucfirst($name) . 'Attr';
        if (method_exists($this, $method)) {
            $value = $this->$method($value, $data);
        } elseif (isset($this->type[$name])) {
            // 类型转换
            $value = $this->writeTransform($value, $this->type[$name]);
        }
        // 设置数据对象属性
        $this->data[$name] = $value;
        return $this;
    }

    /**
     * 获取器 获取数据对象的值
     * @access public
     * @param string $name 名称
     * @return mixed
     */
    public function getAttr($name){
        $value    = $this->getData($name);
        // 检测属性获取器
        $method = 'get' . $name . 'Attr';
        if (method_exists($this, $method)) {
            $value = $this->$method($value, $this->data);
        } elseif (isset($this->type[$name])) {
            // 类型转换
            $value = $this->readTransform($value, $this->type[$name]);
        }
        return $value;
    }

    /**
     * 数据写入 类型转换
     * @access public
     * @param mixed        $value 值
     * @param string|array $type  要转换的类型
     * @return mixed
     */
    protected function writeTransform($value, $type)
    {
        if (is_array($type)) {
            list($type, $param) = $type;
        } elseif (strpos($type, ':')) {
            list($type, $param) = explode(':', $type, 2);
        }
        switch ($type) {
            case 'integer':
                $value = (int) $value;
                break;
            case 'float':
                if (empty($param)) {
                    $value = (float) $value;
                } else {
                    $value = (float) number_format($value, $param);
                }
                break;
            case 'boolean':
                $value = (bool) $value;
                break;
            case 'timestamp':
                if (!is_numeric($value)) {
                    $value = strtotime($value);
                }
                break;
            case 'datetime':
                $format = !empty($param) ? $param : $this->dateFormat;
                $value  = is_numeric($value) ? $value : strtotime($value);
                $value  = $this->formatDateTime($value, $format);
                break;
            case 'object':
                if (is_object($value)) {
                    $value = json_encode($value, JSON_FORCE_OBJECT);
                }
                break;
            case 'array':
                $value = (array) $value;
            case 'json':
                $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE;
                $value  = json_encode($value, $option);
                break;
            case 'serialize':
                $value = serialize($value);
                break;

        }
        return $value;
    }

    /**
     * 数据读取 类型转换
     * @access public
     * @param mixed        $value 值
     * @param string|array $type  要转换的类型
     * @return mixed
     */
    protected function readTransform($value, $type)
    {
        if (is_array($type)) {
            list($type, $param) = $type;
        } elseif (strpos($type, ':')) {
            list($type, $param) = explode(':', $type, 2);
        }
        switch ($type) {
            case 'integer':
                $value = (int) $value;
                break;
            case 'float':
                if (empty($param)) {
                    $value = (float) $value;
                } else {
                    $value = (float) number_format($value, $param);
                }
                break;
            case 'boolean':
                $value = (bool) $value;
                break;
            case 'timestamp':
                if (!is_null($value)) {
                    $format = !empty($param) ? $param : $this->dateFormat;
                    $value  = $this->formatDateTime($value, $format);
                }
                break;
            case 'datetime':
                if (!is_null($value)) {
                    $format = !empty($param) ? $param : $this->dateFormat;
                    $value  = $this->formatDateTime(strtotime($value), $format);
                }
                break;
            case 'json':
                $value = json_decode($value, true);
                break;
            case 'array':
                $value = is_null($value) ? [] : json_decode($value, true);
                break;
            case 'object':
                $value = empty($value) ? new \stdClass() : json_decode($value);
                break;
            case 'serialize':
                $value = unserialize($value);
                break;
            default:
                if (false !== strpos($type, '\\')) {
                    // 对象类型
                    $value = new $type($value);
                }
        }
        return $value;
    }

    /**
     * 时间日期字段格式化处理
     * @access public
     * @param mixed $time      时间日期表达式
     * @param mixed $format    日期格式
     * @param bool  $timestamp 是否进行时间戳转换
     * @return mixed
     */
    protected function formatDateTime($time, $format, $timestamp = false)
    {
        if (false !== strpos($format, '\\')) {
            $time = new $format($time);
        } elseif (!$timestamp && false !== $format) {
            $time = date($format, $time);
        }
        return $time;
    }
}